package com.cc.dlnalib

import android.annotation.SuppressLint
import android.content.Context
import android.media.MediaMetadataRetriever
import android.util.Log
import androidx.annotation.WorkerThread
import com.cc.mydlnademo.getLocalVideoByPath
import com.cc.networkmonitor.NetStateMonitor
import kotlinx.coroutines.*
import org.cybergarage.upnp.ControlPoint
import org.cybergarage.upnp.Device
import java.io.IOException


const val STATE_TRANSITIONING = "TRANSITIONING"
const val STATE_PLAYING = "PLAYING"
const val STATE_STOPPED = "STOPPED"
const val STATE_PAUSED_PLAYBACK = "PAUSED_PLAYBACK"

/**
 * 自定义状态，播放完成
 */
const val STATE_CUSTOM_COMPLETE = "COMPLETE"
const val STATE_GRAB = "grab"
private const val INTERVAL_MAX = 1000L
private const val COMPLETE_LEFT_TIME = 10

@SuppressLint("StaticFieldLeak")
object DLNAController {
    private val tag = "DLNAController"
    private val controller = MultiPointController()
    lateinit var deviceFinder: DeviceFinder
    private val controlPoint = ControlPoint()
    var currentDevice: Device? = null
        private set
    lateinit var ipAddress: String
    var currentState = STATE_STOPPED
    private var progressJob: Job? = null
    private var position: String? = null
    private var duration: String? = null
    private var progressCallBacks = mutableListOf<(progress: String, duration: String) -> Unit?>()
    private var stateCallbacks = mutableListOf<((state: String) -> Unit?)>()
    private var currentUrl: String? = null
    private var curDuration: Long = 0
    var currentPath: String? = null

    @SuppressLint("StaticFieldLeak")
    private val netStateMonitor = NetStateMonitor()
    private val stateMonitor = StateMonitor(controller)

    @SuppressLint("StaticFieldLeak")
    private var context: Context? = null
    fun init(context: Context) {
        this.context = context
        try {
            HttpServer(HttpServer.PORT, context)
        } catch (e: IOException) {
            e.printStackTrace()
        }
        stateMonitor.setCallBack { state ->
            Log.i(tag, "state:$state")
            if (state != currentState) {
                currentState = state
                if (currentState == STATE_PLAYING) {
                    val durationInfo = controller.getMediaDuration(currentDevice)
                    if (durationInfo.duration.isNullOrEmpty()) {
                        durationInfo.duration = getFormatTime(getPlayTime(currentPath))
                    }
                    duration = durationInfo.duration
                    Log.i(tag, "playUrl:" + durationInfo.playUrl)
                    if (durationInfo.playUrl != currentUrl) {
                        currentState = STATE_GRAB
                    } else {
                        startProgress()
                    }
                } else {
                    progressJob?.cancel()
                }
                if (currentState == STATE_STOPPED || currentState == STATE_TRANSITIONING || currentState == STATE_GRAB) {
                    stateMonitor.stop()
                }
                if (currentState == STATE_STOPPED && duration != null && position != null) {
                    val progress = timeToInt(position.toString())
                    val duration = timeToInt(duration.toString())
                    val offset = duration - progress
                    if (duration < COMPLETE_LEFT_TIME || (progress != 0 && duration != 0 && offset <= COMPLETE_LEFT_TIME)) {
                        currentState = STATE_CUSTOM_COMPLETE
                    }
                }
                Log.i(tag, "state changed:$state")
                GlobalScope.launch(Dispatchers.Main) {
                    Log.i(tag, "state onStateChanged")
                    onStateChanged()
                }
            }
        }
        deviceFinder = DeviceFinder(controlPoint)
        if (!context.isWifiConnected()) {
            netStateMonitor.startCompat(context.applicationContext) { isEnable, isWifi ->
                if (isEnable && isWifi) {
                    ipAddress = DLNAUtil.getLocalIpAddress(context)
                }
            }
        } else {
            ipAddress = DLNAUtil.getLocalIpAddress(context)
        }
    }

    private fun onStateChanged() {
        stateCallbacks.forEach {
            it.invoke(currentState)
        }
    }

    fun addStateCallBack(block: (state: String) -> Unit) {
        if (!stateCallbacks.contains(block)) {
            stateCallbacks.add(block)
        }
    }

    fun removeStateCallBack(block: (state: String) -> Unit) {
        stateCallbacks.remove(block)
    }

    private fun startProgress() {
        Log.i(tag, "startProgress")
        progressJob = GlobalScope.launch {
            while (isActive) {
                currentDevice?.let {
                    position = controller.getPositionInfo(it)
                    if (position != null && duration != null)
                        withContext(Dispatchers.Main) {
                            progressCallBacks.forEach {
                                it.invoke(position!!, duration!!)
                            }
                        }
                    delay(1000)
                }
            }
            Log.i(tag, "progress stopped")
        }
    }

    fun play(
        device: Device,
        path: String,
        metaData: String? = null,
        block: ((isSuccess: Boolean) -> Unit)?
    ) {
        currentUrl = buildUrl(path)
        currentPath = path
        progressJob?.cancel()
        duration = null
        position = null
        currentState = ""
        GlobalScope.launch {
            currentDevice = device
            controller.stop(device)
            val isSuccess = controller.play(device, currentUrl, metaData)
            stateMonitor.stop()
            stateMonitor.start(device)
            withContext(Dispatchers.Main) {
                block?.invoke(isSuccess)
            }
        }
    }

    /**
     * 根据url查询视频时长和宽高
     *
     * @param url
     * @return
     */
    fun getPlayTime(url: String?): Int {
        if (url.isNullOrEmpty()) {
            return 0
        }
        var d = context?.getLocalVideoByPath(url)?.diration
        if (d != null && d > 0) {
            d /= 1000
        }
        return d ?: 0
    }


    private fun buildUrl(path: String): String {
        if (path.startsWith("http://")) {
            return path;
        }
        var tempPath = "/"
        if (path.endsWith("mp4", true) || path.endsWith("3gp", true)) {
            tempPath += "1.mp4"
        } else if (path.endsWith("mp3", true) || path.endsWith("flac", true) || path.endsWith(
                "wav",
                true
            )
        ) {
            tempPath += "1.mp3"
        }
        val url = "http://$ipAddress:${HttpServer.PORT}$tempPath"
        return url
    }

    fun startSearch() {
        GlobalScope.launch {
            deviceFinder.search()
        }
    }

    fun addCallBack(block: (MutableList<Device>) -> Unit) {
        deviceFinder.addCallBack(block)
    }

    fun removeCallBack(block: (MutableList<Device>) -> Unit) {
        deviceFinder.removeCallBack(block)
    }

    fun seek(progress: Int) {
        GlobalScope.launch {
            currentDevice?.let {
                controller.seek(currentDevice, getFormatTime(progress))
            }
        }
    }

    fun togglePlay() {
        currentDevice?.also {
            GlobalScope.launch {
                if (currentState == STATE_PAUSED_PLAYBACK) {
                    controller.goon(it, position)
                    stateMonitor.start(currentDevice)
                } else if (currentState == STATE_PLAYING) {
                    controller.pause(it)
                }
            }
        }
    }

    @WorkerThread
    fun getVolumeSync(): Int {
        currentDevice?.let {
            return try {
                controller.getVoice(currentDevice)
            } catch (e: Exception) {
                -1
            }
        }
        return -1
    }

    @WorkerThread
    fun getMaxVolumeSync(): Int {
        currentDevice?.let {
            return try {
                controller.getMaxVolumeValue(currentDevice)
            } catch (e: Exception) {
                -1
            }
        }
        return -1
    }

    @WorkerThread
    fun setVolume(volume: Int) {
        controller.setVoice(currentDevice, volume)
    }

    fun addProgressCallBack(block: (progress: String, duration: String) -> Unit) {
        if (!progressCallBacks.contains(block)) {
            progressCallBacks.add(block)
        }
    }

    fun removeProgressCallBack(block: (progress: String, duration: String) -> Unit) {
        progressCallBacks.remove(block)
    }

    fun release() {
        netStateMonitor.unregisterCompat()
        stateMonitor.stop()
    }
}